
package com.av.torrent.jobs.manager;

import com.av.torrent.jobs.AbstractSubscribableJob;

import android.os.AsyncTask;
import android.util.Log;

import java.util.Queue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Uladzimir_Kerus
 */
public class DefaultJobManager implements JobManager {

  private static final String LOG_TAG = DefaultJobManager.class.getSimpleName();

  private final Queue<ComparableJobHolder> mExecutionQueue = new PriorityBlockingQueue<ComparableJobHolder>();

  private ExecutorTask mExecutorTask;

  private int mMinPriority = 0;

  private int mMaxPriority = 0;

  private final ReentrantLock mStateUpdateLock = new ReentrantLock();

  private final Condition mUpdateCondition = mStateUpdateLock.newCondition();

  /**
   * @param handler
   */
  public DefaultJobManager() {
    super();
  }

  /*
   * (non-Javadoc)
   * @see com.av.torrent.jobs.manager.JobManager#addJob(com.av.torrent.jobs.Job,
   * boolean)
   */
  @Override
  public void addJob(AbstractSubscribableJob<?> job, boolean isToAddToTail) {
    Log.d(LOG_TAG, String.format("In addJob(Job, boolean): adding new job [job = %s, isToAddToTail = %s]",
        String.valueOf(job), String.valueOf(isToAddToTail)));

    int priority = -1;
    if (isToAddToTail) {
      priority = mMaxPriority++;
    } else {
      priority = mMinPriority--;
    }

    mExecutionQueue.add(new ComparableJobHolder(job, priority));

    signalStateUpdated();

    Log.d(LOG_TAG, String.format("In addJob(Job): job added to %s of the queue with priority %d",
        isToAddToTail ? "TAIL" : "HEAD", priority));
  }

  private void signalStateUpdated() {
    mStateUpdateLock.lock();
    try {
      mUpdateCondition.signal();
    } finally {
      mStateUpdateLock.unlock();
    }
  }

  /*
   * (non-Javadoc)
   * @see com.av.torrent.jobs.manager.JobManager#start()
   */
  @Override
  public void start() {
    if (mExecutorTask != null) {
      Log.w(LOG_TAG, "In start(): executor task was ALREADY started");
    } else {
      mExecutorTask = new ExecutorTask();
      mExecutorTask.execute((Void)null);
      Log.d(LOG_TAG, "In start(): executor task was started");
    }
  }

  /*
   * (non-Javadoc)
   * @see com.av.torrent.jobs.manager.JobManager#stop()
   */
  @Override
  public void stop() {
    if (!mExecutorTask.isCancelled()) {
      mExecutorTask.cancel(true);
      signalStateUpdated();
      Log.d(LOG_TAG, "In start(): executor task was canceled");
    } else {
      Log.w(LOG_TAG, "In start(): executor task was ALREADY canceled");
    }
  }

  /**
   * @author Uladzimir_Kerus
   * @param <T>
   */
  private class ComparableJobHolder implements Runnable, Comparable<ComparableJobHolder> {

    private final AbstractSubscribableJob<?> mJob;

    private final int mPriority;

    public ComparableJobHolder(final AbstractSubscribableJob<?> mJob, int mPriority) {
      super();
      this.mJob = mJob;
      this.mPriority = mPriority;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo(ComparableJobHolder another) {
      return mPriority - another.mPriority;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
      mJob.execute();
    }
  }

  private class ExecutorTask extends AsyncTask<Void, Void, Void> {

    /*
     * (non-Javadoc)
     * @see android.os.AsyncTask#doInBackground(Params[])
     */
    @Override
    protected Void doInBackground(Void... params) {
      while (!isCancelled()) {
        mStateUpdateLock.lock();
        ComparableJobHolder jobHolder;
        while (!isCancelled() && (jobHolder = mExecutionQueue.poll()) != null) {
          jobHolder.run();
        }
        mUpdateCondition.awaitUninterruptibly();
        mStateUpdateLock.unlock();
      }
      return null;
    }
  }
}
